/**************************************************************************************

                                        _ooOoo_                                                           
                                       o8888888o                                                          
                                       88" . "88                                                          
                                       (| -_- |)                                                          
                                       O\  =  /O                                                          
                                    ____/`---'\____                                                       
                                  .'  \\|     |\\  `.                                                     
                                 /  \\|||  :  |||//  \                                                    
                                /  _||||| -:- |||||-  \                                                   
                                |   | \\\  -  /// |   |                                                   
                                | \_|  ''\---/''  |   |                                                   
                                \  .-\__  `-`  ___/-. /                                                   
                              ___`. .'  /--.--\  `. . __                                                  
                           ."" '<  `.___\_<|>_/___.'  >'"".                                               
                          | | :  `- \`.;`\ _ /`;.`/ - ` : | |                                            
                          \  \ `-.   \_ __\ /__ _/   .-` /  /                                             
                     ======`-.____`-.___\_____/___.-`____.-'======                                        
                                        `=---='                                                           
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                         
                                佛祖保佑        永无BUG                                                    

**---------------------------------File Info-------------------------------------------
**                                                                                                        
** @file:               i2c_dri_lx.v
** @author:             LeXin567567
** @date:               2024-06-27
** @brief:              
** @problem:              
**                                                                                                        
**-------------------------------------------------------------------------------------
**************************************************************************************/

module i2c_dri_lx

#(
parameter SLAVE_ADDR = 7'b1010_000,
parameter CLK_FREQ = 26'd50_000_000,
parameter I2C_FREQ = 18'd250_000
) 
(
    input clk,
    input rst_n,

    input i2c_exec,
    input bit_ctrl,
    input i2c_rh_wl,
    input [15:0] i2c_addr,
    input [7:0] i2c_data_w,
    output reg [7:0] i2c_data_r,
    output reg i2c_done,
    output reg scl,
    inout sda,
    output reg dri_clk
);
    localparam st_idle   = 8'b0000_0001;
    localparam st_sladdr = 8'b0000_0010;
    localparam st_addr16 = 8'b0000_0100;
    localparam st_addr8  = 8'b0000_1000;
    localparam st_data_wr= 8'b0001_0000;
    localparam st_addr_rd= 8'b0010_0000;
    localparam st_data_rd= 8'b0100_0000;
    localparam st_stop   = 8'b1000_0000;

    reg            sda_dir     ;                     // I2C数据(SDA)方向控制
    reg            sda_out     ;                     // SDA输出信号
    reg            st_done     ;                     // 状态结束
    reg            wr_flag     ;                     // 写标志
    reg    [ 6:0]  cnt         ;                     // 计数
    reg    [ 7:0]  cur_state   ;                     // 状态机当前状态
    reg    [ 7:0]  next_state  ;                     // 状态机下一状态
    reg    [15:0]  addr_t      ;                     // 地址
    reg    [ 7:0]  data_r      ;                     // 读取的数据
    reg    [ 7:0]  data_wr_t   ;                     // I2C需写的数据的临时寄存
    reg    [ 9:0]  clk_cnt     ;                     // 分频时钟计数

    wire          sda_in      ;                      // SDA输入信号
    wire   [8:0]  clk_divide  ;                      // 模块驱动时钟的分频系数


    // SDA 控制
    assign  sda     = sda_dir ?  sda_out : 1'bz;     // SDA数据输出或高阻
    assign sda_in = sda; // SDA 输入
    assign clk_divide = (CLK_FREQ / I2C_FREQ) >> 3; // 时钟分频

    // 生成 I2C 的ScL 四倍频时钟 驱动 I2C 时钟
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            dri_clk <= 1'b1;
            clk_cnt <= 0;
        end
        else if(clk_cnt == clk_divide -1'd1)begin
            clk_cnt <= 10'd0;
            dri_clk <= ~dri_clk;
        end
        else
            clk_cnt <= clk_cnt + 1'd1;
    end

    // 三段式状态机 同步时序描述状态转移
    always @(posedge dri_clk or negedge rst_n) begin
        if(!rst_n)
            cur_state <= st_idle;
        else
            cur_state <= next_state;
    end

    // 组合逻辑描述状态转移
    always @(*) begin
        case (cur_state)
            st_idle:
                if(i2c_exec)    next_state = st_sladdr;
                else            next_state = st_idle;
            st_sladdr:
                if(st_done)
                    if(bit_ctrl)    next_state = st_addr16;
                    else            next_state = st_addr8;
                else            next_state = st_sladdr;
            st_addr16:
                if(st_done)    next_state = st_addr8;
                else            next_state = st_addr16;
            st_addr8:
                if(st_done)
                    if(wr_flag)    next_state = st_addr_rd;
                    else            next_state = st_data_wr;
                else            next_state = st_addr8;
            st_data_wr:
                if(st_done)    next_state = st_stop;
                else            next_state = st_data_wr;
            st_addr_rd:
                if(st_done)    next_state = st_data_rd;
                else            next_state = st_addr_rd;
            st_data_rd:
                if(st_done)    next_state = st_stop;
                else            next_state = st_data_rd;
            st_stop:
                if(st_done)    next_state = st_idle;
                else            next_state = st_stop;
            default:
                next_state = st_idle;
        endcase
    end


//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n) begin
    //复位初始化
    if(!rst_n) begin
        scl        <= 1'b1;
        sda_out    <= 1'b1;
        sda_dir    <= 1'b1;
        i2c_done   <= 1'b0;
        cnt        <= 1'b0;
        st_done    <= 1'b0;
        data_r     <= 1'b0;
        i2c_data_r <= 1'b0;
        wr_flag    <= 1'b0;
        addr_t     <= 1'b0;
        data_wr_t  <= 1'b0;
    end
    else begin
        st_done <= 1'b0 ;
        cnt     <= cnt +1'b1 ;
        case(cur_state)
             st_idle: begin                            // 空闲状态
                scl     <= 1'b1;
                sda_out <= 1'b1;
                sda_dir <= 1'b1;
                i2c_done<= 1'b0;
                cnt     <= 7'b0;
                if(i2c_exec) begin
                    wr_flag   <= i2c_rh_wl ;
                    addr_t    <= i2c_addr  ;
                    data_wr_t <= i2c_data_w;
                end
            end
            st_sladdr: begin                           // 写地址(器件地址和字地址)
                case(cnt)
                    7'd1 : sda_out <= 1'b0;            // 开始I2C
                    7'd3 : scl <= 1'b0;
                    7'd4 : sda_out <= SLAVE_ADDR[6];   // 传送器件地址
                    7'd5 : scl <= 1'b1;
                    7'd7 : scl <= 1'b0;
                    7'd8 : sda_out <= SLAVE_ADDR[5];
                    7'd9 : scl <= 1'b1;
                    7'd11: scl <= 1'b0;
                    7'd12: sda_out <= SLAVE_ADDR[4];
                    7'd13: scl <= 1'b1;
                    7'd15: scl <= 1'b0;
                    7'd16: sda_out <= SLAVE_ADDR[3];
                    7'd17: scl <= 1'b1;
                    7'd19: scl <= 1'b0;
                    7'd20: sda_out <= SLAVE_ADDR[2];
                    7'd21: scl <= 1'b1;
                    7'd23: scl <= 1'b0;
                    7'd24: sda_out <= SLAVE_ADDR[1];
                    7'd25: scl <= 1'b1;
                    7'd27: scl <= 1'b0;
                    7'd28: sda_out <= SLAVE_ADDR[0];
                    7'd29: scl <= 1'b1;
                    7'd31: scl <= 1'b0;
                    7'd32: sda_out <= 1'b0;            // 0:写
                    7'd33: scl <= 1'b1;
                    7'd35: scl <= 1'b0;
                    7'd36: begin
                        sda_dir <= 1'b0;               // 从机应答
                        sda_out <= 1'b1;
                    end
                    7'd37: scl     <= 1'b1;
                    7'd38: st_done <= 1'b1;
                    7'd39: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                    end
                    default :  ;
                endcase
            end
            st_addr16: begin
                case(cnt)
                    7'd0 : begin
                        sda_dir <= 1'b1 ;
                        sda_out <= addr_t[15];         // 传送字地址
                    end
                    7'd1 : scl <= 1'b1;
                    7'd3 : scl <= 1'b0;
                    7'd4 : sda_out <= addr_t[14];
                    7'd5 : scl <= 1'b1;
                    7'd7 : scl <= 1'b0;
                    7'd8 : sda_out <= addr_t[13];
                    7'd9 : scl <= 1'b1;
                    7'd11: scl <= 1'b0;
                    7'd12: sda_out <= addr_t[12];
                    7'd13: scl <= 1'b1;
                    7'd15: scl <= 1'b0;
                    7'd16: sda_out <= addr_t[11];
                    7'd17: scl <= 1'b1;
                    7'd19: scl <= 1'b0;
                    7'd20: sda_out <= addr_t[10];
                    7'd21: scl <= 1'b1;
                    7'd23: scl <= 1'b0;
                    7'd24: sda_out <= addr_t[9];
                    7'd25: scl <= 1'b1;
                    7'd27: scl <= 1'b0;
                    7'd28: sda_out <= addr_t[8];
                    7'd29: scl <= 1'b1;
                    7'd31: scl <= 1'b0;
                    7'd32: begin
                        sda_dir <= 1'b0;               // 从机应答
                        sda_out <= 1'b1;
                    end
                    7'd33: scl     <= 1'b1;
                    7'd34: st_done <= 1'b1;
                    7'd35: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                    end
                    default :  ;
                endcase
            end
            st_addr8: begin
                case(cnt)
                    7'd0: begin
                       sda_dir <= 1'b1 ;
                       sda_out <= addr_t[7];           // 字地址
                    end
                    7'd1 : scl <= 1'b1;
                    7'd3 : scl <= 1'b0;
                    7'd4 : sda_out <= addr_t[6];
                    7'd5 : scl <= 1'b1;
                    7'd7 : scl <= 1'b0;
                    7'd8 : sda_out <= addr_t[5];
                    7'd9 : scl <= 1'b1;
                    7'd11: scl <= 1'b0;
                    7'd12: sda_out <= addr_t[4];
                    7'd13: scl <= 1'b1;
                    7'd15: scl <= 1'b0;
                    7'd16: sda_out <= addr_t[3];
                    7'd17: scl <= 1'b1;
                    7'd19: scl <= 1'b0;
                    7'd20: sda_out <= addr_t[2];
                    7'd21: scl <= 1'b1;
                    7'd23: scl <= 1'b0;
                    7'd24: sda_out <= addr_t[1];
                    7'd25: scl <= 1'b1;
                    7'd27: scl <= 1'b0;
                    7'd28: sda_out <= addr_t[0];
                    7'd29: scl <= 1'b1;
                    7'd31: scl <= 1'b0;
                    7'd32: begin
                        sda_dir <= 1'b0;               // 从机应答
                        sda_out <= 1'b1;
                    end
                    7'd33: scl     <= 1'b1;
                    7'd34: st_done <= 1'b1;
                    7'd35: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                    end
                    default :  ;
                endcase
            end
            st_data_wr: begin                          // 写数据(8 bit)
                case(cnt)
                    7'd0: begin
                        sda_out <= data_wr_t[7];       // I2C写8位数据
                        sda_dir <= 1'b1;
                    end
                    7'd1 : scl <= 1'b1;
                    7'd3 : scl <= 1'b0;
                    7'd4 : sda_out <= data_wr_t[6];
                    7'd5 : scl <= 1'b1;
                    7'd7 : scl <= 1'b0;
                    7'd8 : sda_out <= data_wr_t[5];
                    7'd9 : scl <= 1'b1;
                    7'd11: scl <= 1'b0;
                    7'd12: sda_out <= data_wr_t[4];
                    7'd13: scl <= 1'b1;
                    7'd15: scl <= 1'b0;
                    7'd16: sda_out <= data_wr_t[3];
                    7'd17: scl <= 1'b1;
                    7'd19: scl <= 1'b0;
                    7'd20: sda_out <= data_wr_t[2];
                    7'd21: scl <= 1'b1;
                    7'd23: scl <= 1'b0;
                    7'd24: sda_out <= data_wr_t[1];
                    7'd25: scl <= 1'b1;
                    7'd27: scl <= 1'b0;
                    7'd28: sda_out <= data_wr_t[0];
                    7'd29: scl <= 1'b1;
                    7'd31: scl <= 1'b0;
                    7'd32: begin
                        sda_dir <= 1'b0;               // 从机应答
                        sda_out <= 1'b1;
                    end
                    7'd33: scl <= 1'b1;
                    7'd34: st_done <= 1'b1;
                    7'd35: begin
                        scl  <= 1'b0;
                        cnt  <= 1'b0;
                    end
                    default  :  ;
                endcase
            end
            st_addr_rd: begin                          // 写地址以进行读数据
                case(cnt)
                    7'd0 : begin
                        sda_dir <= 1'b1;
                        sda_out <= 1'b1;
                    end
                    7'd1 : scl <= 1'b1;
                    7'd2 : sda_out <= 1'b0;            // 重新开始
                    7'd3 : scl <= 1'b0;
                    7'd4 : sda_out <= SLAVE_ADDR[6];   // 传送器件地址
                    7'd5 : scl <= 1'b1;
                    7'd7 : scl <= 1'b0;
                    7'd8 : sda_out <= SLAVE_ADDR[5];
                    7'd9 : scl <= 1'b1;
                    7'd11: scl <= 1'b0;
                    7'd12: sda_out <= SLAVE_ADDR[4];
                    7'd13: scl <= 1'b1;
                    7'd15: scl <= 1'b0;
                    7'd16: sda_out <= SLAVE_ADDR[3];
                    7'd17: scl <= 1'b1;
                    7'd19: scl <= 1'b0;
                    7'd20: sda_out <= SLAVE_ADDR[2];
                    7'd21: scl <= 1'b1;
                    7'd23: scl <= 1'b0;
                    7'd24: sda_out <= SLAVE_ADDR[1];
                    7'd25: scl <= 1'b1;
                    7'd27: scl <= 1'b0;
                    7'd28: sda_out <= SLAVE_ADDR[0];
                    7'd29: scl <= 1'b1;
                    7'd31: scl <= 1'b0;
                    7'd32: sda_out <= 1'b1;            // 1:读
                    7'd33: scl <= 1'b1;
                    7'd35: scl <= 1'b0;
                    7'd36: begin
                        sda_dir <= 1'b0;               // 从机应答
                        sda_out <= 1'b1;
                    end
                    7'd37: scl     <= 1'b1;
                    7'd38: st_done <= 1'b1;
                    7'd39: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                    end
                    default : ;
                endcase
            end
            st_data_rd: begin                          // 读取数据(8 bit)
                case(cnt)
                    7'd0: sda_dir <= 1'b0;
                    7'd1: begin
                        data_r[7] <= sda_in;
                        scl       <= 1'b1;
                    end
                    7'd3: scl  <= 1'b0;
                    7'd5: begin
                        data_r[6] <= sda_in ;
                        scl       <= 1'b1   ;
                    end
                    7'd7: scl  <= 1'b0;
                    7'd9: begin
                        data_r[5] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd11: scl  <= 1'b0;
                    7'd13: begin
                        data_r[4] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd15: scl  <= 1'b0;
                    7'd17: begin
                        data_r[3] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd19: scl  <= 1'b0;
                    7'd21: begin
                        data_r[2] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd23: scl  <= 1'b0;
                    7'd25: begin
                        data_r[1] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd27: scl  <= 1'b0;
                    7'd29: begin
                        data_r[0] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd31: scl  <= 1'b0;
                    7'd32: begin
                        sda_dir <= 1'b1;              // 非应答
                        sda_out <= 1'b1;
                    end
                    7'd33: scl     <= 1'b1;
                    7'd34: st_done <= 1'b1;
                    7'd35: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                        i2c_data_r <= data_r;
                    end
                    default  :  ;
                endcase
            end
            st_stop: begin                            // 结束I2C操作
                case(cnt)
                    7'd0: begin
                        sda_dir <= 1'b1;              // 结束I2C
                        sda_out <= 1'b0;
                    end
                    7'd1 : scl     <= 1'b1;
                    7'd3 : sda_out <= 1'b1;
                    7'd15: st_done <= 1'b1;
                    7'd16: begin
                        cnt      <= 1'b0;
                        i2c_done <= 1'b1;             // 向上层模块传递I2C结束信号
                    end
                    default  : ;
                endcase
            end
        endcase
    end
end

endmodule